package bancosys.tec.services.implementor.annotations.visitor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import jmine.tec.di.annotation.visitor.InjectedAnnotationVisitor;
import jmine.tec.di.cache.TypeMatchLookup;
import jmine.tec.di.metadata.DependencyMetaData;
import jmine.tec.di.metadata.impl.DependencyMetaDataImpl;
import jmine.tec.di.resolver.ObjectResolver;
import jmine.tec.di.resolver.impl.DynamicObjectResolver;
import jmine.tec.di.type.NoTypeConverter;
import jmine.tec.di.type.TypeConverter;
import jmine.tec.di.wrapper.Wrapper;
import jmine.tec.di.wrapper.Wrappers;
import jmine.tec.services.annotations.Input;

import org.hibernate.SessionFactory;

import bancosys.tec.services.implementor.descriptor.CachedObjectResolver;

/**
 * AnnotationVisitor para {@link Input}
 * 
 * @author takeshi
 */
public class InputAnnotationVisitor extends SetterAndParametersAnnotationVisitor<DependencyMetaData> {
    private final TypeMatchLookup<TypeConverter<?>> typeConvertion;

    private final TypeMatchLookup<ObjectResolver> objectResolvers;

    /**
     * C'tor
     * 
     * @param typeConvertion o sistema de conversao de tipos
     * @param objectResolvers os resolvedores de objetos estaticos
     */
    public InputAnnotationVisitor(TypeMatchLookup<TypeConverter<?>> typeConvertion, TypeMatchLookup<ObjectResolver> objectResolvers) {
        super(Input.class);
        this.typeConvertion = typeConvertion;
        this.objectResolvers = objectResolvers;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Collection<? extends DependencyMetaData> onParameterAnnotation(Annotation annotation, Class<?> annotated, int parameterIndex,
            Class<?> parameterType, Type genericType, Member annotatedMember) {
        Class<?> componentType = InjectedAnnotationVisitor.resolveGenericType(genericType, annotated);
        return this.createDependencyMetadata(parameterType, (Input) annotation, componentType, "");
    }

    /**
     * Cria uma lista com 1 {@link DependencyMetaData}
     * 
     * @param parameterType o tipo do parametro
     * @param input o {@link Input}
     * @param componentType o tipo do componente
     * @param pName o nome da propriedade
     * @return {@link List} com 1 {@link DependencyMetaData}
     */
    private List<? extends DependencyMetaData> createDependencyMetadata(Class<?> parameterType, Input input, Class<?> componentType,
            String pName) {
        Wrapper w = Wrappers.findWrapperFor(parameterType);
        final String propertyName = "".equals(input.fieldName()) ? pName : input.fieldName();
        final String v = "".equals(input.defaultValue()) ? null : input.defaultValue();
        ObjectResolver resolver = new DynamicObjectResolver(componentType, propertyName, this.findTypeConverterFor(componentType), v);
        if (input.cached()) {
            final long timeout = 10000L;
            resolver = new CachedObjectResolver(resolver, this.objectResolvers.lookup(SessionFactory.class, null), timeout, propertyName);
        }
        return Collections.singletonList(new DependencyMetaDataImpl(propertyName, resolver, input.required(), w, componentType));
    }

    /**
     * Procura um {@link TypeConverter} apropriado
     * 
     * @param componentType Class
     * @return {@link TypeConverter}
     */
    private TypeConverter<?> findTypeConverterFor(Class<?> componentType) {
        return this.typeConvertion.lookup(componentType, NoTypeConverter.getInstance());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected List<? extends DependencyMetaData> onPropertySetterAnnotation(Annotation annotation, Class<?> declaringClass,
            String propertyName, Class<?> propertyRawType, Type genericType) {
        Class<?> componentType = InjectedAnnotationVisitor.resolveGenericType(genericType, declaringClass);
        return this.createDependencyMetadata(propertyRawType, (Input) annotation, componentType, propertyName);
    }

}
